/*
 * conexionBroker.cpp
 *
 *  Created on: 27/07/2014
 *      Author: migue
 */

#include <stdlib.h>
#include <string.h>
#include <map>
#include "../ipc/Socket.h"
#include "../ipc/ColaMensajes.h"
#include "../comun/ServidorRemoto.h"
#include "../defines.h"
#include <syslog.h>
#include <sstream>
#include <unistd.h>

using namespace std;

Tipos::LugarDeSala obtenerLugarSala(int id, int& numSala) {
	if (ID_SHM_SALA_ENT(0) < id && id < ID_SHM_SALA_ZOO(0)) {
		numSala = INV_ID_SHM_SALA_ENT(id);
		return Tipos::LUGAR_ENTRADA;
	} else if (ID_SHM_SALA_ZOO(0) < id) {
		numSala = INV_ID_SHM_SALA_ZOO(id);
		return Tipos::LUGAR_ZOO;
	} else {
		numSala = -1;
		perror("ERROR Obteniendo lugar de sala incorrectamente.");
		syslog(LOG_ERR,
				"ERROR obteniendo lugar y numero de sala incorrectamente a partir de id: %i",
				id);
		*((int*) (NULL)) = 0;
		return Tipos::LUGAR_ENTRADA;
	}
}

std::map<int, int> dirBrokers;
int idCliente;

int buscarBroker(int id) {

	int idBroker = -1;

	if (id == -3)
		return -1;

	/***/ //
	ServidorRemoto serv0;
	serv0.consultarDatos(id);
	return serv0.idBrokerConectado();
	/***/

	if (dirBrokers.find(id) != dirBrokers.end()) {
		idBroker = dirBrokers[id];
	} else {
		ServidorRemoto serv;
		struct sockaddr dir;
		int aux;
		if (serv.consultarDatos(id) == id) {
			serv.datosConexion(idBroker, dir, aux);
			dirBrokers[id] = idBroker;
		} else {
			char error[100];
			sprintf(error,
					"No se encontro broker para enviar paquete, de id: %i", id);
			perror(error);
			*((int*) (NULL)) = 0;
			idBroker = -1;
		}
	}

	return idBroker;
}

void establecerNuevoBroker(int id, int idBrokerNuevo) {
	dirBrokers[id] = idBrokerNuevo;
}

Socket conectarConBroker(bool esReceptor) {
	Socket sock;

	// TODO Hacer...

	return sock;
}

/**
 *	argv[1]: id de la entidad
 *
 *	argv[2]: E o R, si la conexion es para "Enviar" o "Recibir"
 *
 */

void prepararMensajeBroker(MENSAJE& msj, MENSAJE_BROKER& msjBroker) {

	if (msj.tipo != Tipos::MSJ_IPC) {

		int idBrokerDestino = buscarBroker(msj.destinatarioFinal);
		int idBrokerLocal = buscarBroker(msj.origen);

		// TODO revisar ids
		msjBroker.destinatario = idBrokerLocal;
		msjBroker.destinatarioBroker = idBrokerDestino;
		msjBroker.destinatarioFinal = msj.destinatarioFinal;
		msjBroker.origen = msj.origen;

		if (msj.tipo == Tipos::MSJ_NORMAL
				&& msj._ipc.op_ipc == TIPO_FINALIZAR) {

			msjBroker.tipo = TIPO_FINALIZAR;

		} else if (msj.tipo == Tipos::MSJ_ANUNCIAR_BUS_REPUESTO) {

			msjBroker.tipo = TIPO_ESPERAR_BROADCAST;
			msjBroker.id = msj.id;

		} else if (msj.tipo == Tipos::MSJ_PEDIDO_BUS_REPUESTO) {

			msjBroker.tipo = TIPO_LANZAR_BROADCAST;

		} else {

			msjBroker.tipo = TIPO_ENVIO_MSG;
		}

		memcpy(&(msjBroker.mensajeOriginal), &msj, sizeof(MENSAJE));

	} else {
		// mensaje IPC
		int idBrokerLocal = buscarBroker(msj.origen);

		// el destinatario es el broker
		msjBroker.destinatario = idBrokerLocal;
		msjBroker.destinatarioBroker = idBrokerLocal; // antes msj.destinatarioFinal;
		msjBroker.destinatarioFinal = idBrokerLocal; // antes msj.destinatarioFinal;
		msjBroker.origen = msj.origen;

		msjBroker.tipo = msj._ipc.op_ipc;
		msjBroker.idSem = msj._ipc.id_ipc;

		msjBroker.valorAEscribir = msj._ipc.valor;
		msjBroker.numPersona = msj._ipc.posicion_valor;

		int numSala;

		if (msj._ipc.op_ipc >= TIPO_SALA_LEER_CANTPERSONAS
				&& msj._ipc.op_ipc <= TIPO_SALA_ESCRIBIR_ESTADOBUS) {
			msjBroker.lugarSala = obtenerLugarSala(msj._ipc.id_ipc, numSala);
			msjBroker.numSala = numSala;
		} else {
			msjBroker.lugarSala = Tipos::LUGAR_ENTRADA;
			msjBroker.numSala = -999;
		}

	}

	memcpy(&msjBroker.mensajeOriginal, &msj, sizeof(msj));
}

void prepararMensajeCliente(MENSAJE_BROKER& msjBroker, MENSAJE& msj) {

	memcpy(&msj, &msjBroker.mensajeOriginal, sizeof(msj));

	if (msjBroker.tipo == TIPO_ENVIO_MSG) {

		msj.destinatario = msjBroker.destinatarioFinal;
		msj.destinatarioFinal = msjBroker.destinatarioFinal;
		msj.origen = msjBroker.origen;

	} else if (msjBroker.tipo == TIPO_LANZAR_BROADCAST) {

		syslog(LOG_INFO, "LLego Broadcast");

		msj.tipo = Tipos::MSJ_PEDIDO_BUS_REPUESTO;

		msj.destinatario = msjBroker.destinatarioFinal;
		msj.destinatarioFinal = msjBroker.destinatarioFinal;
		msj.origen = msjBroker.origen;
	} else if (msjBroker.tipo >= TIPO_SEM_SIGNAL
			&& msjBroker.tipo <= TIPO_PARQUE_ESCRIBIR_PARQUEABIERTO) {

		msj.destinatario = msjBroker.destinatario;
		msj.origen = msjBroker.origen;
		msj.destinatarioFinal = msjBroker.destinatarioFinal;

		msj.tipo = Tipos::MSJ_IPC;

		msj._ipc.op_ipc = msjBroker.tipo;
		msj._ipc.resp = msjBroker.valorLeido;

		msj._ipc.posicion_valor = msjBroker.numPersona;
		msj._ipc.id_ipc = msjBroker.idSem;
		msj._ipc.valor = msjBroker.valorAEscribir;
	}

}

void generarCorte(int id) {
	stringstream idAux;
	idAux << id;
	int pid = fork();
	if (pid == 0) {
		execlp("./conexBrokerCliente", "conexBrokerCliente",
				idAux.str().c_str(), "E", NULL);
		perror("Error en el exec conexionBrokerCliente");
		exit(1);
	}
	pid = fork();
	if (pid == 0) {
		execlp("./conexBrokerCliente", "conexBrokerCliente",
				idAux.str().c_str(), "R", NULL);
		perror("Error en el exec conexionBrokerCliente");
		exit(1);
	}
}

int main(int argc, char** argv) {

	if (argc < 3) {
		// mostrar error de falta de argumentos y salir
		return 1;
	}

	idCliente = atoi(argv[1]);
	bool enviar = false;

	char encabezado[80];
	sprintf(encabezado, "conexionBrokerCliente %s %i", argv[2], idCliente);
	openlog(encabezado, LOG_PID | LOG_CONS, LOG_LOCAL0);

	if (strcmp("E", argv[2]) == 0 || strcmp("e", argv[2]) == 0) {
		enviar = true;
	} else if (strcmp("R", argv[2]) == 0 || strcmp("r", argv[2]) == 0) {
		enviar = false;
	} else {
		// mostrar error de argumento incorrecto
		return -1;
	}

	ServidorRemoto serv;

	ColaMensajes cCliente(RUTA, COLARECIBO_CLI);
	ColaMensajes cSistema(RUTA, COLASISTEMA);

	if (serv.consultarDatos(idCliente) != idCliente) {
		perror(
				"ERROR no se pudo encontrar datos del cliente en el servidor rpc");
		return -1;
	}

	int idBroker;
	struct sockaddr dirBroker;
	int puertoBroker;

	serv.datosConexion(idBroker, dirBroker, puertoBroker);

	if (idBroker < 0) {
		// mostrar error y salir
		return -1;
	}

	Socket sock;

	MENSAJE_BROKER msjBroker;

	if (sock.connectTo(&dirBroker, puertoBroker) != 0) {
		// mostrar error al conectar con broker y salir
		return -1;
	}

	syslog(LOG_INFO, "Conectado a %i", idBroker);

	// Hago el handshake
	msjBroker.esReceptor = enviar;
	msjBroker.id = idCliente;

	sock.send(&msjBroker, sizeof(msjBroker));

	// handshake finalizado
	signal(SIGPIPE, SIG_IGN );

	bool seguirProcesando = true;
	MENSAJE msj;
	MENSAJE ultimoMensaje;
	memset(&ultimoMensaje, 0, sizeof(MENSAJE));
//	bool reenviar = false;
	unsigned int msgID = 0;

	while (seguirProcesando) {

		if (enviar) {

			cSistema.recibir(&msj, TAM_MENSAJE, idCliente);

			if (msj._ipc.op_ipc == TIPO_FINALIZAR) {
				seguirProcesando = false;
//				if (reenviar) {
				if (msj.idMensaje != msgID) {
					cSistema.enviar(&ultimoMensaje, TAM_MENSAJE);
				}
			}

			prepararMensajeBroker(msj, msjBroker);

			memcpy(&ultimoMensaje, &msj, sizeof(MENSAJE));
			if (msj.tipo == Tipos::MSJ_IPC
					|| msj.tipo == Tipos::MSJ_ANUNCIAR_BUS_REPUESTO) {

//				reenviar = true; //todo
				msj.idMensaje = msgID;
				msgID++;
			}

			sock.send(&msjBroker, sizeof(msjBroker));
		} else {

			int leido = sock.recv(&msjBroker, sizeof(msjBroker));
			if ((unsigned) leido < sizeof(MENSAJE_BROKER)) {
				seguirProcesando = false;
			}

			if (leido == 0) {
				//Broker caido
				int nuevoBroker = serv.informarBrokerCaido(idCliente, idBroker);
				syslog(LOG_INFO, "Cambiando a broker %i", nuevoBroker);
				msj.destinatario = idCliente;
				msj._ipc.op_ipc = TIPO_FINALIZAR;
				msj.idMensaje = msgID;

				cSistema.enviar(&msj, TAM_MENSAJE);

				generarCorte(idCliente);

			} else {

				if (msjBroker.tipo == TIPO_FINALIZAR) {
					seguirProcesando = false;
				} else {
					prepararMensajeCliente(msjBroker, msj);

					if (msj.tipo == Tipos::MSJ_IPC
							|| msj.tipo == Tipos::MSJ_PEDIDO_BUS_REPUESTO) {

//						reenviar = false;
						msj.idMensaje = msgID;
						msgID++;
					}

					cCliente.enviar(&msj, TAM_MENSAJE);
				}
			}

		}
	}

	sock.closeSock();

	return 0;
}
